/*
 Copyright 2007 Razvan Taranu

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package generators;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.thoughtworks.qdox.model.BeanProperty;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
import com.thoughtworks.qdox.model.Type;

public class Beans {
    private final static Log log = LogFactory.getLog(Beans.class);

    public static String beansPackage; // contains the path to the beans

    public static Set<JavaSource> serializableJavaSources = new HashSet<JavaSource>();
    public static Set<String> serializableClassNames = new HashSet<String>();
    public static List<BeanInfo> beanList = new ArrayList<BeanInfo>();

    /**
     * Checks if the given java source contains a serializable bean or not.
     * 
     * @param javaSource
     * @return
     */
    public static boolean checkSerializable(JavaSource javaSource) {
        String sourcePackage = javaSource.getPackage().getName();

        if (false == beansPackage.equals(sourcePackage)) {
            log.debug("source package [" + sourcePackage + "] != java.beans.package (" + beansPackage + ")");
            return false;
        }

        JavaClass[] classes = javaSource.getClasses();
        if (classes.length != 1) {
            log.debug(javaSource.getURL() + " has inner classes");
            return false;
        }

        JavaClass javaClass = classes[0];

        boolean isSerializable = false;
        for (Type implemented : javaClass.getImplements()) {
            if (implemented.getValue().equals("com.google.gwt.user.client.rpc.IsSerializable")) {
                isSerializable = true;
            } else if (implemented.getValue().equals("java.io.Serializable")) {
                isSerializable = true;
            }
        }
        if (false == isSerializable) {
            log.debug(javaClass.getFullyQualifiedName() + " does not implement Serializable or IsSerializable");
            return false;
        }

        String className = javaClass.getFullyQualifiedName();
        log.info(className + " is serializable");

        serializableClassNames.add(javaClass.getFullyQualifiedName());
        serializableJavaSources.add(javaSource);
        
        return true;
    }

    public static void createBeanCache(JavaSource javaSource) {
        BeanInfo beanInfo = new BeanInfo();

        beanInfo.setJavaSource(javaSource);
        JavaClass javaClass = javaSource.getClasses()[0];
        beanInfo.setJavaClass(javaClass);

        Map<BeanProperty, String> genericTypes = new HashMap<BeanProperty, String>();
        List<BeanProperty> properties = new ArrayList<BeanProperty>();
        for (BeanProperty prop : javaClass.getBeanProperties(true)) {
            if (prop.getAccessor() == null) {
                log.warn(prop.getName() + " is write only, ignoring.");
                continue;
            }
            if (prop.getMutator() == null) {
                log.warn(prop.getName() + " is read only, ignoring.");
                continue;
            }

            properties.add(prop);

            String genericType = getGenericClassName(prop.getType());
            genericTypes.put(prop, genericType);

        }
        beanInfo.setProperties(properties);
        beanInfo.setGenericTypes(genericTypes);

        beanList.add(beanInfo);
    }

    final private static Pattern genericTypePattern = Pattern.compile("(.+)<(.+)>");

    public static String getGenericClassName(Type type) {
        String typeValue = type.getValue();
        String genericValue = type.getGenericValue();

        log.debug("parsing generic type for: " + genericValue);

        Matcher matcher = genericTypePattern.matcher(genericValue);
        if (matcher.matches()) {
            if (false == matcher.group(1).trim().equals(typeValue)) {
                // should not happen .. unless the regex is buggy
                throw new RuntimeException("generic type value does not match type value");
            }

            String[] split = matcher.group(2).trim().split("\\s*,\\s*");

            if (Map.class.getName().equals(typeValue)) {
                if (split.length != 2) {
                    throw new RuntimeException("generic parameter count is not valid: " + genericValue);
                }

                if (false == String.class.getName().equals(split[0])) {
                    throw new RuntimeException("map key must be java.lang.String: " + split[0]);
                }

                return split[1];

            } else if (List.class.getName().equals(typeValue)) {
                if (split.length != 1) {
                    throw new RuntimeException("generic parameter count is not valid: " + genericValue);
                }

                return split[0];

            } else {
                throw new RuntimeException("generic values are only supported for List and Map types");

            }
        } else if (Map.class.getName().equals(typeValue) || List.class.getName().equals(typeValue)) {
            throw new RuntimeException("expecting generic parameters for type: " + genericValue);

        } else {
            return null;

        }
    }

    public static boolean isSerializable(JavaSource javaSource) {
        return serializableJavaSources.contains(javaSource);
    }

    public static boolean isSerializable(String className) {
        return serializableClassNames.contains(className);
    }
}
